#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <limits.h>
#include "../../../clsv/ipc/sig.h"
#include "../../../clsv/error.h"
#include "msgADT.h"

/*--------------------------------------------------------------------------*/

/* Message priorities */
/* Priority 0 will cause msgsend to fail */
#define MSG_SRV_SND	1
#define MSG_SRV_RCV	2
#define MSG_CLI_SND	MSG_SRV_RCV
#define MSG_CLI_RCV	MSG_SRV_SND


#define MSG_MAX	1024
#define MSG_PERMS	0666

/* Initial message, connection identifier */
#define MSG_CONN		22
#define MSG_DISC		23
#define MSG_WAIT_DISC	8

/*--------------------------------------------------------------------------*/

struct msgCDT
{
	int key;		/* Message queue key value */
	int id;			/* Message queue ID */
	/* Send and receive intended to use priorities intead of 2 queues */
	long snd;		/* Priority to use to send a message */
	long rcv;		/* Priority to use to receive */
};

typedef struct Tmsg
{
	long mtype;		/* Message type */
	char mtext[MSG_MAX+1];	/* Message text */
} Tmsg;

/*--------------------------------------------------------------------------*/

static int openQueue(int key);

static int createQueue(int key);

static int initMsgADT(int key, msgADT *msg);

static void doNothing(int sig);

/*--------------------------------------------------------------------------*/

int keyqueue;

extern int errno;

extern int counter;

static int interrupted;

/*--------------------------------------------------------------------------*/

/* Common initialization of msgADT */
static int
initMsgADT(int key, msgADT *msg)
{
	msgADT aux;

	*msg = NULL;

	if ( (aux = malloc(sizeof(struct msgCDT)) ) == NULL )
	{
		fprintf(stderr,"msgADT: Initiating msgADT, malloc error.\n");
		return ERROR_MALLOC; /* Not enough memory */
	}

	aux->key = key;

	*msg = aux;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

/* Connect to an existing queue */
/* Returns the queue ID or less than zero if error */
static int
openQueue(int key)
{
	int error = ERROR_OK;

	if( (error = msgget(key,0)) == -1)
	{
		if( errno == EACCES ) /* No permission */
		{
			perror("msgADT: Opening queue, acces denied, check queue permissions.\n");
			error = ERROR_ACCES;
		}
		else if( errno == ENOENT ) /* No message queue exists */
		{
			perror("msgADT: Opening queue, message queue does not exists.\n");
			error = ERROR_EXIST;
		}
		else /* Not enough memory or system limit reached */
		{
			perror("msgADT: Opening queue, unexpected error.\n");
			error = ERROR_UNEX;
		}
	}

	return error;
}

/*--------------------------------------------------------------------------*/

/* Create a new queue */
/* Returns the queue ID or less than zero if error */
static int
createQueue(int key)
{
	int error = ERROR_OK;

	if( (error = msgget( (key_t)key, IPC_CREAT | MSG_PERMS | IPC_EXCL)) == -1)
	{
		if( errno == EEXIST ) /* Queue already exists */
		{
			printf("CREO KEY= (%d)\n", key);
			perror("msgADT: Creating queue, message queue already exists.\n");
			error = ERROR_EXIST;
		}
		else /* Not enough memory or system limit reached */
		{
			perror("msgADT: Creating queue, unexpected error.\n");
			error = ERROR_UNEX;
		}
	}

	return error;
}

/*--------------------------------------------------------------------------*/

int
connMsg(int key, msgADT *msg)
{
	int buf;
	int ans;
	int error = ERROR_OK;

	/* Common initialization */
	ans = initMsgADT((key_t)key, msg);
	if( ans != ERROR_OK )
		return ans;

	/* Open existing queue */
	if( (ans = openQueue(key)) < 0 )
	{
		error = ans;
	}
	else
	{	/* Save the queue ID */
		(*msg)->id = ans;

		/* Set msgADT parameters for the client */
		(*msg)->snd = MSG_CLI_SND;
		(*msg)->rcv = getpid();

		/* Now connect */
		/* Send initial message */
		buf = getpid();
		ans = sndMsg(*msg,&buf,sizeof(buf));

		if(  ans != ERROR_OK )
		{
			error = ans;
		}
		else
		{
			/* Receive new queue ID */
			
			rcvMsg(*msg,&buf,sizeof(buf));
			(*msg)->snd = LONG_MAX-buf;
			
			ans = rcvMsg(*msg,&buf,sizeof(buf));

			if( ans < sizeof(buf) )
			{
				if( ans < 0 )
					error = ans;

				else
				{
					fprintf(stderr,"msgADT: Connecting, readed less bytes than expected.\n");
					error = ERROR_UNEX;
				}
			}
			else
			{
				/* Open the server's new queue */
				if( (ans = openQueue(buf)) < 0 )
				{
					error = ans;
				}
				else
				{
					(*msg)->id = ans;
				}
			}
		}
	}

	if( error != ERROR_OK )
	{
		free(*msg);
		*msg = NULL;
	}

	return error;
}

/*--------------------------------------------------------------------------*/

/* Create a new queue to start listening */
int
bindMsg(int key, msgADT *msg)
{
	int error = ERROR_OK;
	
	/* Common initialization */
	error = initMsgADT(key, msg);
	if( error != ERROR_OK )
		return error;

	/* Create new queue */
	error = createQueue(key);
	if( error < 0 )
	{
		free(*msg);
		*msg = NULL;
		return error;
	}
	else
	{
		/* Save the queue ID */
		(*msg)->id = error;
		/* Set msgADT parameters for the server */
		(*msg)->snd = MSG_SRV_SND;
		(*msg)->rcv = MSG_SRV_RCV;
	}
	/* Create new queue */
	error = createQueue(key+10);
	if( error < 0 )
	{
		free(*msg);
		*msg = NULL;
		return error;
	}
	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

int
acceptMsg(msgADT msg, msgADT *new)
{
	int key;
	int buf;
	int error = ERROR_OK;

	/* Receive a client */
	if( rcvTMsg( msg, &buf, sizeof(buf), 0)  < sizeof(buf) )
		return ERROR_UNEX;

	/* Get new queue Key */
	key = msg->key + 10;
	error = initMsgADT(key, new);
	if( error != ERROR_OK )
		return error;

	/* Save the queue key */
	(*new)->key = key;
	
	/* Save the queue ID */
	(*new)->id = msgget(key, 0);

	/* Set msgADT parameters for the server */
	(*new)->snd = buf;
	(*new)->rcv = LONG_MAX-counter;

	/* Send new queue to client */
	msg->snd= buf;

	sndMsg(msg,&counter,sizeof(counter));
	
	error = sndMsg(msg,&key,sizeof(key));
	if(  error != ERROR_OK)
		return ERROR_UNEX;

	msg->snd= MSG_SRV_SND;

	return ERROR_OK;
}

/*--------------------------------------------------------------------------*/

/* Returns the number of bytes actually copied into buf, or less than zero */
int
rcvTMsg(msgADT msg, void *buf, int count, int timeout)
{
	Tmsg msgData;
	int signal = 0;
	int ans;
	int error = ERROR_OK;

	/* Check parameters */
	/* Count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	if(count > MSG_MAX)
		return ERROR_MAX;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Receive message, waiting if necessary */
	/* MSG_MAX specifies the maximum size for mtext of msgData structure */
	/* Check if signal caugth. errno = EINTR */
	do
	{
		signal = 0;
		interrupted = 0;

		if(timeout)
			setAlarm(timeout, doNothing);

		ans = msgrcv(msg->id, &msgData, MSG_MAX, (long)msg->rcv, MSG_NOERROR);

		if(timeout)
			unsetAlarm();

		if( ans == -1 )
		{
			if( errno == EACCES )
			{
				perror("msgADT: Receiving, acces denied, check queue permissions.\n");
				error = ERROR_ACCES;
			}
			else if( errno == EINTR && !interrupted)
			{
				signal = 1;
			}
			else
			{
				perror("msgADT: Receiving, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
		else
		{
			error = ans;
			/* Put message data in buf */
			memcpy(buf, msgData.mtext, count);
		}
	}while( signal );

	return error;
}

/*--------------------------------------------------------------------------*/

/* Returns 0, less if error */
int
sndTMsg(msgADT msg, void *buf, int count, int timeout)
{
	Tmsg msgData;
	int signal = 0;
	int ans;
	int error = ERROR_OK;

	/* Check parameters */
	/* count */
	if( count < 0 )
		return ERROR_PARAMS;
	if( count == 0 )
		return ERROR_OK;
	if(count > MSG_MAX)
		return ERROR_MAX;
	/* buf */
	if( buf == NULL )
		return ERROR_PARAMS;

	/* Initialiaze the message */
	msgData.mtype = (long)msg->snd;
	memcpy(msgData.mtext, buf, count);

	/* Send message, waiting if necessary */
	/* Check if signal caugth. errno = EINTR */
	do
	{
		signal = 0;
		interrupted = 0;

		if(timeout)
			setAlarm(timeout, doNothing);

		ans = msgsnd(msg->id, &msgData, count, 0);

		if(timeout)
			unsetAlarm();

		if(  ans == -1)
		{
			if( errno == EACCES )
			{
				perror("msgADT: Sending, acces denied, check queue permissions.\n");
				error = ERROR_ACCES;
			}
			else if( errno == EINTR && !interrupted)
			{
				signal = 1;
			}
			else
			{
				perror("msgADT: Sending, unexpected error.\n");
				error = ERROR_UNEX;
			}
		}
		else
		{
			error = ans;
		}
	}while( signal );

	return error;
}

/*--------------------------------------------------------------------------*/

int
rcvMsg(msgADT msg, void *buf, int count)
{
	return rcvTMsg(msg, buf, count, MSG_WAIT_DISC);
}

/*--------------------------------------------------------------------------*/

int sndMsg(msgADT msg, void *buf, int count)
{
	return sndTMsg(msg, buf, count, MSG_WAIT_DISC);
}

/*--------------------------------------------------------------------------*/

int closeMsg(msgADT msg)
{
	struct msqid_ds msq_status;
	int ans;
	int buf;
	int error = ERROR_OK;

	if( msg != NULL )
	{
	/* You are the creator of the queue */
	/* Wait for a final message */
	if( msg->snd == 1 )
	{
			/* Receive client's disconnect message. With a timeout */
			ans = rcvMsg(msg, &buf, sizeof(buf)) < sizeof(buf);

			/* Delete queue */
			ans = msgctl(msg->id, IPC_RMID, &msq_status);
			if(  ans == -1)
			{
				if( errno == EACCES )
				{
					perror("msgADT: Closing, acces denied, check queue permissions.\n");
					error = ERROR_ACCES;
				}
				else
				{
					perror("msgADT: Closing, unexpected error.\n");
					error = ERROR_UNEX;
				}
			}
		}
		else
		{
			buf = MSG_DISC;
			ans = sndMsg(msg, &buf, sizeof(buf));
		}

		free(msg);
	}

	return error;

}

/*--------------------------------------------------------------------------*/

static void
doNothing(int s)
{
//	fprintf(stderr,"msgADT: Closing, disconnect message was not received.\n");
	interrupted = 1;
}
